#include "URI.h"
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>

namespace qy
{

URI::URI(const std::string &url)
{
	m_port = 0;
	if(parse(url) == false)
	{
		m_scheme = "";
		m_username = "";
		m_password = "";
		m_host = "";
		m_path.clear();
		m_query.clear();
		m_fragement = "";
		m_port = 0;
	}
}

URI::~URI()
{

}

std::string URI::scheme() const
{
	return m_scheme;
}

std::string URI::username() const
{
	return m_username;
}

std::string URI::password() const
{
	return m_password;
}

std::string URI::host() const
{
	return m_host;
}

unsigned int URI::port() const
{
	return m_port;
}

std::string URI::path() const
{
	std::string str;
	for(std::list<std::string>::const_iterator it = m_path.begin(); it != m_path.end(); it++ )
	{
		if(str.length() > 0) str.append(1, '/');
		str += *it;
	}
	return str;
}

std::string URI::fragement() const
{
	return m_fragement;
}

std::string URI::fullPath() const
{
	std::string result;
	if(m_scheme.length() == 0)
	{
		return "";
	}

	result = m_scheme + "://";

	if(m_username.length() > 0)
	{
		result += m_username;
		if(m_password.length() > 0)
		{
			result += ":" + m_password;
		}
		result += "@";
	}

	result += m_host;
	if(m_port != 0)
	{
		char portStr[10];
		memset(portStr, 0, sizeof(char) * 10);
		sprintf(portStr, "%d", m_port);
		if(strlen(portStr) > 0)
		{
			result += ":" + std::string(portStr);
		}
	}

	for(std::list<std::string>::const_iterator it = m_path.begin(); it != m_path.end(); it++)
	{
		result += "/" + *it;
	}

	if(m_query.size() > 0)
	{
		result += "?";
		std::map<std::string, std::string>::const_iterator it = m_query.begin(); 
		for(;it != m_query.end(); it++)
		{
			result += it->first + "=" + it->second + "&";
		}
		if(result[result.length() - 1] == '&')
		{
			result.erase(result.length() - 1, 1);
		}
	}

	if(m_fragement.length() > 0)
	{
		result += "#" + m_fragement;
	}

	return result;

}

bool URI::parse(const std::string &url)
{
	int status = 0;
	std::string str;
	std::string key;
	size_t i = 0;
	size_t len = url.length();
	for(i = 0; i < len; i++)
	{
		char ch = url[i];
		switch(status)
		{
		case 0:
			if(isalnum(ch)) str.append(1, ch);
			else if(ch == ':' && str.length() > 0 && url.length() > i + 2 && url.substr(i + 1, 2) == "//")
			{
				m_scheme = str;
				str.clear();
				i += 2;
				status = 1;
			}
			else return false;
			break;
		case 1:
			if(ch == ':' || ch == '@')
			{
				if(str.length() == 0) return false;
				else
				{
					m_username = str;
					str.clear();
					status = (ch == ':' ? 2 : 3);
				}
			}
			else if(ch == '.')
			{
				str.append(1, ch);
				status = 4;
			}
			else if(valid(ch)) str.append(1, ch);	
			else return false;
			break;
		case 2:
			if(ch == '@')
			{
				if(str.length() == 0) return false;
				else
				{
					m_password = str;
					str.clear();
					status = 3;
				}
			}
			else if(valid(ch)) str.append(1, ch);
			else return false;
			break;
		case 3:
			if(ch == '.')
			{
				str.append(1, ch);
				status = 4;
			}
			else if(valid(ch))
			{
				str.append(1, ch);
			}
			else return false;
			break;
		case 4:
			if(valid(ch))	
			{
				str.append(1, ch);
				status = 10;
			}
			else return false;
			break;
		case 5:
			if(valid(ch)) str.append(1, ch);
			else if(ch == '&' || ch == ';' || ch == '#')
			{
				if(key.length() > 0 && str.length() > 0)
				{
					m_query[key] = str;
				}
				str.clear();
				key.clear();
				if(ch == '#') status = 9;
				else status = 8;
			}
			else return false;
			break;
		case 6:
			if(ch == '/' || ch == '?' || ch == '#')
			{
				m_port = atoi(str.c_str());
				if(ch == '/') status = 7;
				else if(ch == '?') status = 8;
				else status = 9;
				str.clear();
			}
			else if(isdigit(ch)) str.append(1, ch);
			else return false;
			break;
		case 7:
			if(valid(ch))
			{
				str.append(1, ch);
				status = 11;
			}
			else if(ch == '?') status = 8;
			else if(ch == '#') status = 9;
			else return false;
			break;
		case 8:
			if(ch == '#') status = 9;
			else if(ch == '=')
			{
				if(str.length() > 0 && key.length() == 0)
				{
					key = str;
					str.clear();
					status = 5;
				}
				else return false;
			}
			else if(valid(ch)) str.append(1, ch);
			else return false;
			break;
		case 9:
			if(valid(ch)) str.append(1, ch);
			else return false;
			break;
		case 10:
			if(ch == '.') 
			{
				str.append(1, ch);
				status = 4;
			}
			else if(ch == '/' || ch == ':' || ch == '?' || ch == '#')
			{
				if(str.length() == 0) return false;
				m_host = str;
				str.clear();
				if(ch == ':') status = 6;
				else if(ch == '/') status = 7;
				else if(ch == '?') status = 8;
				else status = 9;
			}
			else if(valid(ch)) str.append(1, ch);
			else return false;
			break;
		case 11:
			if(valid(ch)) str.append(1, ch);
			else if(ch == '/' || ch == '#' || ch == '?')
			{
				m_path.push_back(str);
				str.clear();
				if(ch == '/') status = 7;
				else if(ch == '#') status = 9;
				else status = 8;
			}
			else return false;
			break;
		default:
			return false;
		}
	}
	if(status == 10)
	{
		if(str.length() > 0) 
		{
			m_host = str;
			str.clear();
			return true;
		}
	}
	else if(status == 5)
	{
		if(str.length() > 0 && key.length() > 0)
		{
			m_query[key] = str;
			key.clear();
			str.clear();
		}
		return true;
	}
	else if(status == 6)
	{
		if(str.length() > 0)
		{
			m_port = atoi(str.c_str());
			str.clear();
			return true;
		}
	}
	else if(status == 8)
	{
		//the last key hasn't value, just ignore this error.
		return true;
	}
	else if(status == 9)
	{
		if(str.length() > 0)
		{
			m_fragement = str;
			str.clear();
		}
		return true;
	}
	else if(status == 11)
	{
		if(str.length() > 0)
		{
			m_path.push_back(str);
			str.clear();
			return true;
		}
	}
	return false;
}

bool URI::valid(char ch)
{
	std::string invalid = ":/#&?;";
	if(invalid.find(ch) != std::string::npos) return false;
	else if(isspace(ch)) return false;
	else return true;
}

}

